package com.zh.filetransport.core

import com.zh.filetransport.indicator.ProgressIndicator
import com.zh.filetransport.indicator.ProgressIndicatorFactory
import com.zh.filetransport.model.DATA_KB
import com.zh.filetransport.model.FileMetadata
import com.zh.filetransport.model.FileTask
import com.zh.filetransport.model.gson
import com.zh.mycommon.util.MyFilenameUtil.getSaveFilePath
import kotlinx.coroutines.*
import org.tinylog.Logger
import java.io.IOException
import java.net.InetSocketAddress
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.channels.ClosedChannelException
import java.nio.channels.FileChannel
import java.nio.channels.ServerSocketChannel
import java.nio.channels.SocketChannel
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.StandardOpenOption
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.concurrent.thread

private val log = Logger.tag("FileServerKt")

var receiveCallback: (FileTask) -> Unit = {
    println("正在接收文件 ${it.filePath}")
}

// 文件服务器类
class FileServer(private val port: Int, private val mode: String) {
    private val isRunning = AtomicBoolean(false)
    private lateinit var serverSocketChannel: ServerSocketChannel

    private val scope = CoroutineScope(Dispatchers.IO)
    private val jobs = ConcurrentLinkedQueue<Job>()

    // 启动服务器
    fun start(receive: ((FileTask) -> Unit)? = null) {
        if (receive != null) {
            receiveCallback = receive
        }

        isRunning.set(true)
        //启动任务监听线程
        GetFileSender().listeningTask().start()

        thread {
            doStart()
        }
    }

    fun doStart() {
        // 服务端ServerSocket通道
        serverSocketChannel = ServerSocketChannel.open().apply {
            bind(InetSocketAddress(port))
            configureBlocking(true) // 阻塞模式
            log.info("文件服务器已启动，监听端口: $port","")
        }


        while (isRunning.get()) {
            try {
                //监听客户端Socket通道
                //当有新的连接时：它返回一个包含新进来的连接的 SocketChannel
                val clientChannel: SocketChannel = serverSocketChannel.accept()

                val job = scope.launch {
                    handleClient(clientChannel)
//                    println("处理完成一个连接.")
                }
                jobs.add(job)
                jobs.removeIf { it.isCompleted } // 移除已完成的job，避免内存泄漏
            } catch (e: ClosedChannelException) {
                log.info("closed server channel ...","")
                break
            } catch (e: IOException) {
                log.warn("客户端连接失败. err=" + e.message,"")
            }
        }

        // 统一在这里写释放资源的代码
        apply {
            // 取消所有活跃的协程
            jobs.forEach { it.cancel() }
            jobs.clear()

            serverSocketChannel.close()
            // 关闭协程作用域
            scope.cancel()
            println("文件服务器已关闭.")

            isRunning.set(false)
        }
    }

    // 处理客户端连接
    private suspend fun handleClient(socketChannel: SocketChannel) {
        val st = System.currentTimeMillis()

        withContext(Dispatchers.IO) {
            val taskMapProcess = ConcurrentHashMap<String, ProgressIndicator>()
            var transferredSizeCounter: Long = 0

            val intBuffer = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN)
            val metaBuffer = ByteBuffer.allocate(4 * DATA_KB).order(ByteOrder.BIG_ENDIAN)

            try {
                while (true) {
                    intBuffer.clear()
                    metaBuffer.clear()

                    // 读取元数据长度
                    while (intBuffer.hasRemaining()) {
                        if (socketChannel.read(intBuffer) == -1) { //读取结束了
                            val cost = (System.currentTimeMillis() - st).toFloat() / 1000
                            log.info("【任务结束】此次传输耗时:  ${cost}(S)","")
                            return@withContext
                        }
                    }


                    intBuffer.flip()
                    val metaLen = intBuffer.getInt()

                    // 读取元数据
                    metaBuffer.limit(metaLen) // 设置本次需要读取的字节数上限
                    while (metaBuffer.hasRemaining()) {
                        socketChannel.read(metaBuffer) //读取网络中的数据到buffer中
                    }
                    metaBuffer.flip()

                    val jsonBytes = ByteArray(metaBuffer.remaining())
                    metaBuffer.get(jsonBytes) //数据从缓冲区读取到数组

                    val metaJson = String(jsonBytes, StandardCharsets.UTF_8)
                    val metadata = gson.fromJson(metaJson, FileMetadata::class.java)
//                    println("debug >>> metaJson = $metaJson")

                    // 创建进度条
                    val rootDir = metadata.getRootDir()
                    val key = "$rootDir:${metadata.totalSize}"

                    if (!taskMapProcess.containsKey(key)) {
                        val f = if (metadata.isDir()) rootDir else metadata.name
                        val remoteIp = socketChannel.remoteAddress.toString()

                        val task = FileTask(
                            id = UUID.randomUUID().toString().takeLast(8),
                            filePath = f,
                            remoteIp = remoteIp,
                            totalSize = metadata.totalSize,
                            isDir = metadata.isDir()
                        )

                        val progressIndicator = ProgressIndicatorFactory.create(mode)
                        progressIndicator.bind(task.id, f, true)
                        progressIndicator.accept(0, task.totalSize)

                        taskMapProcess[key] = progressIndicator
                        transferredSizeCounter = 0

                        log.debug("receive task : file=${task.filePath}, from=${task.remoteIp}","")
                        receiveCallback(task)
                    }

                    // 构建输出文件路径
                    val outputPath = getSaveFilePath(metadata.basePath, metadata.name)
                    Files.createDirectories(Paths.get(outputPath).parent)

                    // 创建新文件
                    FileChannel.open(
                        Paths.get(outputPath),
                        StandardOpenOption.CREATE,
                        StandardOpenOption.WRITE
                    ).use {
                        //读取网络中的文件数据，写入本地文件
                        var transferred = 0L
                        while (transferred < metadata.size) {
                            val n = it.transferFrom(socketChannel, 0, metadata.size - transferred)
                            if (n == 0L) {
                                // 在阻塞模式下，只有当文件大小为0或连接已关闭时才会返回0
                                throw IOException("socket连接已断开，无法继续传输")
                            }
                            transferred += n
                        }
                        if (transferred != metadata.size) {
                            log.error("transferred和metadata.size不一致","")
                        }
                        transferredSizeCounter += transferred
                        // 更新进度
                        taskMapProcess[key]?.accept(transferredSizeCounter, metadata.totalSize)
                    }
                }

            } catch (e: Exception) {
                log.error(e.message,"")
            } finally {
                try {
                    socketChannel.isConnected
                    socketChannel.finishConnect()
                    socketChannel.close()
                } catch (e: IOException) {
                    log.error(e.message,"")
                }
                taskMapProcess.clear()
            }
        }
    }

    // 停止服务器
    fun shutdown() {
        if (isRunning.get()) {
            GetFileSender().listeningTask().interrupt()
            serverSocketChannel.close()
            isRunning.set(false)
        }
    }

}

fun NewFileService(port: Int, mode: String): FileServer {
    createFileSender(port, mode)
    return FileServer(port, mode)
}
